package com.gpj.learn.springmvc.gpjmvc.servlet;

import com.gpj.learn.springmvc.gpjmvc.annotations.Controller;
import com.gpj.learn.springmvc.gpjmvc.annotations.RequestMapping;
import com.gpj.learn.springmvc.gpjmvc.annotations.ResponseBody;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

@Slf4j
public class GDispatcherServlet extends HttpServlet {
	private static final String CLASSPATH = GDispatcherServlet.class.getClassLoader().getResource("").getPath();
	private static final String INIT_PARAMER_XML = "xml";

	private static final String XML_ELEMENT_NAME_COMPENTSCAN = "compentScan";
	private static final String XML_ELEMENT_NAME_VIEW = "view";

	private Map<String, Method> methodMap = new HashMap<>();

	private String prefix;
	private String suffix;

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) {
		doPost(request, response);
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) {
		// 通过请求路径获取对应方法
		String requestURI = request.getRequestURI();
		Method method = methodMap.get(requestURI);

		if (method == null) {
			response.setStatus(404);
			return;
		}


		try {
			// 将请求参数封装为方法参数
			Object[] params = generateRequestParam(request, response, method.getParameters());

			// 执行方法
			Object obj = method.getDeclaringClass().newInstance();
			method.setAccessible(true);
			Object result = method.invoke(obj, params);

			// 返回响应
			if (!method.getReturnType().equals(Void.class)) {
				if (method.isAnnotationPresent(ResponseBody.class)) {
					response.getWriter().write(String.valueOf(result));
				} else {
					request.getRequestDispatcher(prefix + String.valueOf(result) + suffix).forward(request, response);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			response.setStatus(500);
		}


	}

	private Object[] generateRequestParam(
	        HttpServletRequest request, HttpServletResponse response,
            Parameter[] parameters) throws IllegalAccessException, InstantiationException {
		Object[] params = new Object[parameters.length];

		for (int i = 0; i < parameters.length; i++) {
			Parameter parameter = parameters[i];

			Class<?> clazz = parameter.getType();
			String name = parameter.getName();

			if (clazz == String.class) {
				params[i] = request.getParameter(name);
			} else if (clazz == HttpServletRequest.class) {
				params[i] = request;
			} else if (clazz == HttpServletResponse.class) {
				params[i] = response;
			} else if (isBasicClass(clazz) || isWrapClass(clazz)) {
				log.error("暂不支持[{}]该类型转换", clazz.getName());
				throw new RuntimeException("参数转换失败");
			} else {
				Object obj = clazz.newInstance();

				for (Field field : clazz.getDeclaredFields()) {
					if (field.getType() != String.class) {
						log.warn("暂不支持对象中[{}]类型属性的转换", field.getType().getName());
					}
					field.setAccessible(true);
					field.set(obj, request.getParameter(field.getName()));
				}
				params[i] = obj;
			}
		}
		return params;
	}


	/**
	 * 判断是否为基础类型
	 * @param clazz 待判断类型
	 */
	private boolean isBasicClass(Class clazz) {
		return clazz.isPrimitive();
	}

	/**
	 * 判断是否是基础数据类型的包装类型
	 * @param clazz 待判断类型
	 */
	private boolean isWrapClass(Class clazz) {
		try {
			return ((Class) clazz.getField("TYPE").get(null)).isPrimitive();
		} catch (Exception e) {
			return false;
		}
	}

	@Override
	public void init(ServletConfig config) {
		log.info("gpjmvc starting");

		// 解析xml配置文件
		String xml = config.getInitParameter(INIT_PARAMER_XML);
		Map<String, Element> xmlConfig = parse(xml);

		prefix = xmlConfig.get(XML_ELEMENT_NAME_VIEW).attribute("prefix").getValue();
		suffix = xmlConfig.get(XML_ELEMENT_NAME_VIEW).attribute("suffix").getValue();

		// 找到扫描路径
		String scanPath = xmlConfig.get(XML_ELEMENT_NAME_COMPENTSCAN).attribute("package").getValue();
		// 扫描指定路径,找到路由信息
		List<Class> list = scan(scanPath);

		// 解析类的Url信息
		parseUrl(list);

		log.info("gpjmvc inited");
	}

	/**
	 * 解析xml文件,找到配置信息
	 * @param xmlName xml配置文件的文件名
	 */
	private Map<String, Element> parse(String xmlName) {
		Map<String, Element> map = new HashMap<>();

		try {
			SAXReader saxReader = new SAXReader();
			Document read = saxReader.read(new File(CLASSPATH + xmlName));
			Element rootElement = read.getRootElement();
			Iterator<Element> iterator = rootElement.elementIterator();
			while (iterator.hasNext()) {
				Element ele = iterator.next();
				map.put(ele.getName(), ele);
			}
		} catch (DocumentException e) {
			e.printStackTrace();
		}
		return map;
	}

	private List<Class> scan(String scanPath) {
		try {
			scanPath = scanPath.replaceAll("\\.", "\\\\");
			return scan(scanPath, new File(CLASSPATH + scanPath));
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return Collections.emptyList();
	}

	private List<Class> scan(String scanPath, File file) throws ClassNotFoundException {
		List<Class> list = new ArrayList<>();
		if (file.isDirectory()) {
			for (File childFile : file.listFiles()) {
				list.addAll(scan(scanPath, childFile));
			}
		} else {
			int index = file.getPath().indexOf(scanPath);
			String className = file.getPath().substring(index, file.getPath().length() - 6);
			className = className.replaceAll("\\\\", "\\.");
			Class<?> clazz = this.getClass().getClassLoader().loadClass(className);

			if (clazz.isAnnotationPresent(Controller.class)) {
				list.add(clazz);
			}
		}
		return list;
	}

	private void parseUrl(List<Class> list) {
		for (Class clazz : list) {
			String classUrl = "";
			if (clazz.isAnnotationPresent(RequestMapping.class)) {
				RequestMapping annotation = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
				classUrl = annotation.value();
			}

			for (Method method : clazz.getDeclaredMethods()) {
				if (method.isAnnotationPresent(RequestMapping.class)) {
					RequestMapping annotation = method.getAnnotation(RequestMapping.class);
					String methodUrl = annotation.value();
					if (methodUrl.startsWith("/")) {
						methodUrl = methodUrl.substring(1);
					}
					methodMap.put(classUrl + "/" + methodUrl, method);
				}
			}
		}
	}

}
